#include <graphics/image2d.h>
#include <graphics/image3d.h>

#include <QPixmap>
#include <assert.h>
#include <QDebug>

namespace yam3d
{
namespace graphics
{


Image2D::Image2D(bool convertToRGBA)
    : m_convertToRGBA(convertToRGBA)
{
}

Image2D::Image2D( const char* const filename, bool convertToRGBA )
    : m_convertToRGBA(convertToRGBA)
{
    load(filename);
}

Image2D::Image2D( int w, int h, bool convertToRGBA )
    : m_convertToRGBA(convertToRGBA)
{
    createBuf(w,h);
}

Image2D::~Image2D()
{
}

void Image2D::load( const core::string& filename )
{
    QImage image(filename);

    if (image.isNull()) {
        qDebug("Failed to load image: %s", filename.toStdString().c_str());
        return;
    }

    if( m_convertToRGBA )
    {
        image = image.convertToFormat(QImage::Format_ARGB32);
    }
    else
    {
        image = image.convertToFormat(QImage::Format_RGB888);
    }

    createBuf(image.width(),image.height());

    for ( int y = 0; y<height(); y++ )
    {
       for( int x = 0; x<width(); x++ )
       {
           QRgb pix = image.pixel(x,y);

           getPixel(x,y)[0] = qRed(pix);
           getPixel(x,y)[1] = qGreen(pix);
           getPixel(x,y)[2] = qBlue(pix);
           if( m_convertToRGBA )
           {
                getPixel(x,y)[3] = qAlpha(pix);
           }
       }
    }
}

unsigned char* Image2D::getPixel( int x, int y )
{
    assert( x >= 0 && x < width() );
    assert( y >= 0 && y < height() );

    int index(0);
    if( m_convertToRGBA )
    {
        index = x*4+y*width()*4;
    }
    else
    {
        index = x*3+y*width()*3;
    }

    return &m_buf[index];
}

int Image2D::width() const
{
    return m_w;
}

int Image2D::height() const
{
    return m_h;
}

int Image2D::getFormat() const
{
    if( m_convertToRGBA )
    {
        return 4;
    }
    else
    {
        return 3;
    }
}

Image2D* Image2D::genMipMap2D()
{
   Image2D* res = new Image2D(width()/2,height()/2, m_convertToRGBA);

   for ( int y = 0; y<res->height(); y++ )
   {
       for( int x = 0; x<res->width(); x++ )
       {
           struct Pos2D
           {
               int x; int y;
           };

           // Compute the offsets for 2x2 grid of pixels in previous image to perform box filter
           Pos2D srcIndex[4] =
           {
               { (x*2),       (y*2),    },
               { (x*2),       (y*2) + 1 },
               { (x*2) + 1,   (y*2),    },
               { (x*2) + 1,   (y*2) + 1 }
           };



           float r = 0.0f,
           g = 0.0f,
           b = 0.0f,
           a = 0.0f;



           // Sum all pixels
           for(int sample = 0; sample<4; sample++ )
           {
               Pos2D sampleVal = srcIndex[sample];
               r += getPixel(sampleVal.x,sampleVal.y)[0];
               g += getPixel(sampleVal.x,sampleVal.y)[1];
               b += getPixel(sampleVal.x,sampleVal.y)[2];
               if( m_convertToRGBA )
               {
                   a += getPixel(sampleVal.x,sampleVal.y)[3];
               }
           }

           // Average results
           r /= 4.0f;
           g /= 4.0f;
           b /= 4.0f;
           a /= 4.0f;

           // Store resulting pixels
           res->getPixel(x,y)[0] = core::byte(r);
           res->getPixel(x,y)[1] = core::byte(g);
           res->getPixel(x,y)[2] = core::byte(b);
           if( m_convertToRGBA )
           {
                res->getPixel(x,y)[3] = core::byte(a);
           }
       }
   }

   return res;
}


Image3D::Image3D()
{
}

Image3D::Image3D( int w, int h, int d )
{
    createBuf(w,h,d);
}

Image3D::~Image3D()
{
}

void Image3D::setPixelColor( int x, int y, int z, int col )
{
    getPixel(x,y,z)[0] = (0xff000000 & col) >> 24;
    getPixel(x,y,z)[1] = (0x00ff0000 & col) >> 16;
    getPixel(x,y,z)[2] = (0x0000ff00 & col) >> 8;
    getPixel(x,y,z)[3] = (0x000000ff & col) >> 0;
}

unsigned char* Image3D::getPixel( int x, int y, int z )
{
    assert( x >= 0 && x < width() );
    assert( y >= 0 && y < height() );
    int index = x*4 + y*width()*4 + z*width()*height()*4;
    return &m_buf[index];
}

int Image3D::width() const
{
    return m_w;
}

int Image3D::height() const
{
    return m_h;
}

int Image3D::depth() const
{
    return m_d;
}

Image3D* Image3D::genMipMap3D()
{
   Image3D* res = new Image3D(width()/2,height()/2,depth()/2);

    for( int z = 0; z<res->depth(); z++ )
    {
        for ( int y = 0; y<res->height(); y++ )
        {
            for( int x = 0; x<res->width(); x++ )
            {
                struct Pos3D
                {
                    int x; int y; int z;
                };

                // Compute the offsets for 2x2x2 grid of pixels in previous image to perform box filter
                Pos3D srcIndex[8] =
                {
                    { (x*2),       (y*2),      (z*2)       },
                    { (x*2),       (y*2),      (z*2) + 1   },
                    { (x*2),       (y*2) + 1,  (z*2)       },
                    { (x*2),       (y*2) + 1,  (z*2) + 1   },
                    { (x*2) + 1,   (y*2),      (z*2)       },
                    { (x*2) + 1,   (y*2),      (z*2) + 1   },
                    { (x*2) + 1,   (y*2) + 1,  (z*2)       },
                    { (x*2) + 1,   (y*2) + 1,  (z*2) + 1   }
                };



                float r = 0.0f,
                g = 0.0f,
                b = 0.0f,
                a = 0.0f;



                // Sum all pixels
                for(int sample = 0; sample<8; sample++ )
                {
                    Pos3D sampleVal = srcIndex[sample];
                    r += getPixel(sampleVal.x,sampleVal.y,sampleVal.z)[0];
                    g += getPixel(sampleVal.x,sampleVal.y,sampleVal.z)[1];
                    b += getPixel(sampleVal.x,sampleVal.y,sampleVal.z)[2];
                    a += getPixel(sampleVal.x,sampleVal.y,sampleVal.z)[3];
                }

                // Average results
                r /= 8.0f;
                g /= 8.0f;
                b /= 8.0f;
                a /= 8.0f;

                // Store resulting pixels
                res->getPixel(x,y,z)[0] = core::byte(r);
                res->getPixel(x,y,z)[1] = core::byte(g);
                res->getPixel(x,y,z)[2] = core::byte(b);
                res->getPixel(x,y,z)[3] = core::byte(a);
            }
        }
    }

   return res;
}


}
}

